package com.sojson.util.token;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletResponse;

import com.sojson.base.impl.BaseUser;
import com.sojson.config.exception.TransErrorCode;
import com.sojson.constant.Constant;
import com.sojson.constant.ConstantBean;
import com.sojson.constant.ConstantByMap;
import com.sojson.enums.core.EDisableAtType;
import com.sojson.enums.core.EGeneralStatus;
import com.sojson.enums.core.ELoginMoreOnlineType;
import com.sojson.enums.core.ERoleType;
import com.sojson.enums.core.EStatusLogin;
import com.sojson.project.sys.log.entity.po.LogOper;
import com.sojson.project.sys.log.service.ISysLogService;
import com.sojson.project.sys.user.entity.bo.UserOnline;
import com.sojson.util.DateUtil;
import com.sojson.util.ExpUtil;
import com.sojson.util.JwtUtil;
import com.sojson.util.SpringUtil;
import com.sojson.util.StringUtil;
import com.sojson.util.cache.CacheUtil;
import com.sojson.util.param.ParamUtil;
import com.sojson.util.properties.PropertiesUtil;
import com.sojson.util.servlet.ServletUtil;
import com.sojson.util.text.Convert;

import io.jsonwebtoken.Claims;

/**
 * token管理工具类
 *
 * @author liu
 * @date 2020-11-02
 */
public class TokenUtil {

    /** 前端传过来的参数在请求域中的Key */
    public static final String AUTHENTICATION_TOKEN = "AUTHENTICATION_TOKEN";
    /** 免密登录标记 */
    public static final String PWD_NO = "PWD_NO";
    /** Token超时时间,单位毫秒,默认是3个月: 1000 * 60 * 60 * 24 * 30 * 3 */
    public static final long TOKEN_TIMEOUT =
        Convert.toLong(PropertiesUtil.getNumberFormat(Constant.PROPERTIES.getProperty("so.token.timeout")),
            1000L * 60 * 60 * 24 * 30 * 3);
    /** Token下一次超时时间,单位毫秒,默认是30分钟: 1000 * 60 * 30 */
    public static final long TOKEN_TIMEOUT_NEXT =
        Convert.toLong(PropertiesUtil.getNumberFormat(Constant.PROPERTIES.getProperty("so.token.timeout.next")),
            1000L * 60 * 30);
    /** Token下一次超时时间,单位秒 */
    public static final int TOKEN_TIMEOUT_NEXT_SECOND = (int)(TOKEN_TIMEOUT_NEXT / 1000);
    /** 剩余多少时间更新Token,单位毫秒,默认是10分钟: 1000 * 60 * 10 */
    private static final long TOKEN_REFRESH_TIME_REMAINING = Convert.toLong(
        PropertiesUtil.getNumberFormat(Constant.PROPERTIES.getProperty("so.token.refresh.timeRemaining")),
        1000L * 60 * 30);
    /** 所有权限标识 */
    public static final String ALL_PERMISSION = "/**";
    /** 角色名称列表 */
    public static Map<String, String> ROLE_NAMES = new HashMap<>();
    /** 角色Key列表 */
    public static Map<String, String> ROLE_KEYS = new HashMap<>();
    /** 权限ID组 */
    public static Map<String, Set<String>> PERM_IDS = new HashMap<>();
    /** 权限ID组 */
    public static Map<String, Set<String>> PERM_IDS_EXIST = new HashMap<>();
    /** 权限组 */
    public static Map<String, Set<String>> PERMS = new HashMap<>();

    public static ISysLogService LOG_FACTORY = SpringUtil.getBeanByType(ISysLogService.class);

    /**
     * 创建Claims字符串
     *
     * @return Claims
     */
    private static String createClaimsStr() {
        // 创建Token字符串
        String parseJwt = JwtUtil.createJWT();

        // 保存到响应头中
        setClaimsStrToResHeader(parseJwt);

        // 创建Token对象
        return parseJwt;
    }

    /**
     * 获取当前的Claims字符串
     *
     * @return Claims
     */
    public static String getClaimsStr() {
        return getClaimsStr(false);
    }

    /**
     * 获取当前的Claims字符串
     *
     * @param isCreate true:不存在就创建, false:存在就获取
     * @return
     */
    public static String getClaimsStr(boolean isCreate) {
        // 先从响应头中获取Token
        String claimsStr = getResHeader(ConstantByMap.TOKEN_NAME);
        if (isBlankObject(claimsStr)) {
            // 从请求头中获取Token
            claimsStr = getReqHeader(ConstantByMap.TOKEN_NAME);
            // 如果获取不到就创建
            if (isBlankString(claimsStr)) {
                // 如果允许创建就创建,不允许创建就返回null
                if (isCreate) {
                    return createClaimsStr();
                } else {
                    return null;
                }
            }
            // 保存到响应头
            setClaimsStrToResHeader(claimsStr);
        }

        // 不允许创建就直接返回,返回的要么已经解析好的,要么就是null
        return claimsStr;
    }

    /**
     * 获取当前的Claims字符串并验证有效性
     *
     * @param isCreate true:不存在就创建, false:存在就获取
     * @return
     */
    public static String getClaimsStrVerify() {
        return getClaimsStrVerify(false);
    }

    /**
     * 获取当前的Claims字符串并验证有效性
     *
     * @param isCreate true:不存在就创建, false:存在就获取
     * @return
     */
    public static String getClaimsStrVerify(boolean isCreate) {
        // 获取当前的Claims字符串
        String claimsStr = getClaimsStr(isCreate);

        // 如果不是空就验证有效性
        if (isNotBlankObject(claimsStr)) {
            JwtUtil.parseJWT(claimsStr);
        }

        return claimsStr;
    }

    /**
     * 创建Claims
     *
     * @return Claims
     */
    private static Claims createClaims() {
        // 创建Token字符串
        String parseJwt = createClaimsStr();

        // 创建Token对象
        return JwtUtil.parseJWT(parseJwt);
    }

    /**
     * 获取当前的Claims
     *
     * @return
     */
    public static Claims getClaims() {
        return getClaims(false);
    }

    /**
     * 获取当前的Claims
     *
     * @param claimsStr Token字符串
     * @return
     */
    public static Claims getClaims(String claimsStr) {
        return getClaims(claimsStr, false);
    }

    /**
     * 获取当前的Claims
     *
     * @param isCreate true:不存在就创建, false:存在就获取
     * @return
     */
    public static Claims getClaims(boolean isCreate) {
        // 先从响应头中获取Token
        String claimsStr = getClaimsStr(isCreate);
        if (isBlankObject(claimsStr)) {
            return null;
        }

        return getClaimsPublic(claimsStr, isCreate);
    }

    /**
     * 获取当前的Claims
     *
     * @param claimsStr Token字符串
     * @param isCreate  true:不存在就创建, false:存在就获取
     * @return
     */
    public static Claims getClaims(String claimsStr, boolean isCreate) {
        Claims claimsPublic = getClaimsPublic(claimsStr, isCreate);
        // 没解析出来就结束
        if (isBlankObject(claimsPublic)) {
            return null;
        }

        // 保存到响应头
        setClaimsStrToResHeader(claimsStr);
        return claimsPublic;
    }

    /**
     * 获取当前的Claims
     *
     * @param claimsStr Token字符串
     * @param isCreate  true:不存在就创建, false:存在就获取
     * @return
     */
    private static Claims getClaimsPublic(String claimsStr, boolean isCreate) {
        // 获取到了就解析
        Claims parseJwt = JwtUtil.parseJWT(claimsStr);
        // 没解析出来或解析错误,并且允许创建就创建
        if (isBlankObject(parseJwt) && isCreate) {
            return createClaims();
        }
        // 不允许创建就直接返回,返回的要么已经解析好的,要么就是null
        return parseJwt;
    }

    /**
     * 获取当前的TokenId
     *
     * @return
     * @throws IOException
     */
    public static String getTokenId() throws IOException {
        return getTokenId(false);
    }

    /**
     * 获取当前的TokenId
     *
     * @param isCreate true:不存在就创建, false:存在就获取
     * @return
     * @throws IOException
     */
    public static String getTokenId(boolean isCreate) throws IOException {
        return getClaims(isCreate).getId();
    }

    /**
     * 获取当前的Token
     *
     * @return
     * @throws IOException
     */
    public static Token getToken() throws IOException {
        return getToken(false);
    }

    /**
     * 获取当前的Token
     *
     * @param isCreate true:不存在就创建, false:存在就获取
     * @return
     * @throws IOException
     */
    public static Token getToken(boolean isCreate) throws IOException {
        // 先从请求域中获取Token,如果获取不到就从缓存中获取
        Object valRequest = getValRequest(ConstantByMap.TOKEN_REQUEST_NAME);
        if (isNotBlankObject(valRequest)) {
            return (Token)valRequest;
        }

        // 获取Claims
        Claims claims = getClaims(isCreate);

        // 如果不是null就从缓存中获取Token
        if (isNotBlankObject(claims)) {
            return getTokenPublic(claims.getId(), isCreate);
        }

        // 如果是null就返回
        return null;
    }

    /**
     * 获取当前的Token
     *
     * @param tokenId   TokenId
     * @return
     * @throws IOException
     */
    public static Token getTokenById(String tokenId) throws IOException {
        return getTokenById(tokenId, false);
    }

    /**
     * 获取当前的Token
     *
     * @param tokenId   TokenId
     * @param isCreate  true:不存在就创建, false:存在就获取
     * @return
     * @throws IOException
     */
    public static Token getTokenById(String tokenId, boolean isCreate) throws IOException {
        // 先从请求域中获取Token,如果获取不到就从缓存中获取
        Object valRequest = getValRequest(ConstantByMap.TOKEN_REQUEST_NAME);
        if (isNotBlankObject(valRequest)) {
            return (Token)valRequest;
        }

        return getTokenPublic(tokenId, isCreate);
    }

    /**
     * 获取别人的Token
     *
     * @param tokenId   TokenId
     * @return
     */
    public static Token getTokenOtherById(String tokenId) {
        Token token = get(getTokenKey(tokenId));
        if (token != null) {
            token.setRoles(null);
            token.setPermissions(null);
        }
        return token;
    }

    /**
     * 获取当前的Token
     *
     * @param claimsStr Token字符串
     * @return
     * @throws IOException
     */
    public static Token getTokenByClaimsStr(String claimsStr) throws IOException {
        return getTokenByClaimsStr(claimsStr, false);
    }

    /**
     * 获取当前的Token
     *
     * @param claimsStr Token字符串
     * @param isCreate  true:不存在就创建, false:存在就获取
     * @return
     * @throws IOException
     */
    public static Token getTokenByClaimsStr(String claimsStr, boolean isCreate) throws IOException {
        Claims claims = getClaims(claimsStr, isCreate);
        if (isBlankObject(claims)) {
            return null;
        }

        return getTokenById(claims.getId(), isCreate);
    }

    /**
     * 获取当前的Token
     *
     * @param tokenId   TokenId
     * @param isCreate  true:不存在就创建, false:存在就获取
     * @return
     * @throws IOException
     */
    private static Token getTokenPublic(String tokenId, boolean isCreate) throws IOException {
        // 如果不是null就通过ClaimsId获取Token
        Token token = getTokenOtherById(tokenId);

        // 如果获取到了就保存到请求域中并返回
        if (isNotBlankObject(token)) {
            // 保存到请求域中
            setValRequest(ConstantByMap.TOKEN_REQUEST_NAME, token);
            return token;
        } else {
            // 如果没获取到并且允许创建,就创建
            // 如果允许创建
            if (isCreate) {
                // 创建Token
                return createToken();
            }
            return null;
        }
    }

    /**
     * 删除Token
     *
     * @throws IOException
     */
    public static void removeToken() throws IOException {
        removeToken(getClaims().getId());
    }

    /**
     * 删除Token
     *
     * @param id    TokenId
     * @throws IOException
     */
    public static void removeToken(String id) throws IOException {
        // 从请求域中删除Token
        removeValRequest(ConstantByMap.TOKEN_REQUEST_NAME);
        // 从缓存中删除Token
        removeTokenOther(id);
    }

    /**
     * 删除别人的Token
     *
     * @param id    TokenId
     * @throws IOException
     */
    public static void removeTokenOther(String id) throws IOException {
        // 从缓存中删除Token
        delByKey(getTokenKey(id));
    }

    /**
     * 更新Token
     *
     * @param token
     * @throws IOException
     */
    public static boolean refreshToken(Token token) throws IOException {
        // 刷新在线用户
        kickout(token);

        // 刷新Token
        refreshBasicToken(token);
        return true;
    }

    /**
     * 更新Token
     *
     * @param token
     */
    private static void refreshBasicToken(Token token) {
        // 刷新时间
        token.setLastAccess(System.currentTimeMillis());
        token.setExpireTimeNext(getExpireTimeNext(token.getLastAccess()));

        // 更新缓存中的Token
        setex(getTokenKey(token.getId()), token, TOKEN_TIMEOUT_NEXT_SECOND);
        // 更新请求域中的Token
        setValRequest(ConstantByMap.TOKEN_REQUEST_NAME, token);
    }

    /**
     * 更新别人的Token
     *
     * @param token
     * @throws IOException
     */
    private static void refreshTokenOther(Token token) throws IOException {
        // 更新缓存中的Token
        setex(getTokenKey(token.getId()), token, TOKEN_TIMEOUT_NEXT_SECOND);
    }

    /**
     * 验证并更新Token
     *
     * @throws IOException
     */
    public static boolean verifyRefreshToken() throws IOException {
        Claims claims = getClaims();
        if (isBlankObject(claims)) {
            return false;
        }
        Token token = getTokenById(claims.getId());
        if (isBlankObject(token)) {
            return true;
        }
        return verifyRefreshToken(token);
    }

    /**
     * 验证并更新Token
     *
     * @param id ClaimsId
     * @throws IOException
     */
    public static boolean verifyRefreshToken(String id) throws IOException {
        Token token = getTokenById(id);
        if (isBlankObject(token)) {
            return true;
        }

        return verifyRefreshToken(token);
    }

    /**
     * 验证并更新Token
     *
     * @param token
     * @throws IOException
     */
    public static boolean verifyRefreshToken(Token token) throws IOException {
        // 验证Token
        boolean verifyToken = verifyToken(token);
        // Token无效就不刷新
        if (!verifyToken) {
            return false;
        }

        /**
         * 判断是否已经踢出
         */
        if (token.getStatusKickout()) {
            logoutSimple(token);
            ExpUtil.throwEx(TransErrorCode.USER_KICKED_OUT);
        }

        // 如果没到更新时间就结束
        Long expireTimeNext = token.getExpireTimeNext();
        long currentTime = System.currentTimeMillis();
        if (expireTimeNext - currentTime > TOKEN_REFRESH_TIME_REMAINING) {
            return true;
        }

        // 刷新Token
        refreshToken(token);
        return true;
    }

    /**
     * 验证Token有效期
     *
     * @param token Token
     * @return true:有效, false:无效
     * @throws IOException
     */
    public static boolean verifyToken(Token token) throws IOException {
        boolean submitLoginIsNormal = submitLoginIsNormal(token.getUser());
        if (!submitLoginIsNormal) {
            removeToken(token.getId());
            return submitLoginIsNormal;
        }

        boolean isSuccess = token.getExpireTime() > System.currentTimeMillis();
        if (!isSuccess) {
            removeToken(token.getId());
        }
        isSuccess = token.getExpireTimeNext() > System.currentTimeMillis();
        if (!isSuccess) {
            removeToken(token.getId());
        }
        return isSuccess;
    }

    /**
     * 创建Token
     *
     * @return
     * @throws IOException
     */
    public static Token createToken() throws IOException {
        // 创建TokenId
        Claims claims = createClaims();

        // 创建
        Token otherInit = new Token().otherInit();
        otherInit.setId(claims.getId());

        // 保存到缓存中
        refreshBasicToken(otherInit);
        return otherInit;
    }

    /**
     * 创建Token
     *
     * @return
     * @throws IOException
     */
    public static Token createToken(Claims claims) throws IOException {
        // 创建
        Token otherInit = new Token().otherInit();
        otherInit.setId(claims.getId());

        // 保存到缓存中
        refreshBasicToken(otherInit);
        return otherInit;
    }

    /**
     * 获取Token在缓存中的全名
     *
     * @param id
     * @return
     */
    private static String getTokenKey(String id) {
        return ConstantByMap.TOKEN_CACHE_PREFIX + id;
    }

    /**
     * 获取Token过期时间
     *
     * @param now   当前系统时间戳
     * @return
     */
    public static long getExpireTime(long now) {
        return now + TOKEN_TIMEOUT;
    }

    /**
     * 获取Token下一次过期时间
     *
     * @param now   当前系统时间戳
     * @return
     */
    public static long getExpireTimeNext(long now) {
        return now + TOKEN_TIMEOUT_NEXT;
    }

    /**
     * 更新Token中的用户
     *
     * @param user
     * @throws IOException
     */
    public static Token refreshTokenUser(BaseUser user) throws IOException {
        Token token = getToken();
        token.setUser(user);

        return refreshTokenUser(token, user);
    }

    /**
     * 更新Token中的用户
     *
     * @param token
     * @param user
     * @throws IOException
     */
    public static Token refreshTokenUser(Token token, BaseUser user) throws IOException {
        token.setUser(user);

        refreshToken(token);

        return token;
    }

    /** 设置登录参数 */
    public static void setLoginParam(Object obj) {
        ServletUtil.getHttpServletRequest().setAttribute(AUTHENTICATION_TOKEN, obj);
    }

    /** 获取登录参数 */
    public static TokenDto getLoginParam() {
        return (TokenDto)ServletUtil.getHttpServletRequest().getAttribute(AUTHENTICATION_TOKEN);
    }

    /**
     * 标记为免密登录(后续不需要判断密码)
     */
    public static void pwdNo() {
        ServletUtil.getHttpServletRequest().setAttribute(PWD_NO, true);
    }

    /**
     * 判断是否为免密登录
     */
    public static boolean pwdIsNo() {
        Object attribute = ServletUtil.getHttpServletRequest().getAttribute(PWD_NO);
        return StringUtil.isNotBlankObject(attribute) && Boolean.parseBoolean(attribute.toString());
    }

    /**
     * 退出登录(只退出Token)
     *
     * @return
     * @throws IOException
     */
    public static void logoutSimple() throws IOException {
        Token token = getToken();
        logoutSimple(token);
    }

    /**
     * 退出登录(只退出Token)
     *
     * @param token
     * @throws IOException
     */
    public static void logoutSimple(Token token) throws IOException {
        if (StringUtil.isNotBlankObject(token)) {
            // 删除用户缓存记录
            removeToken(token.getId());
        }
    }

    /**
     * 退出登录
     *
     * @return
     * @throws IOException
     */
    public static Token logout() throws IOException {
        // 获取Token
        Token token = getToken();

        return logout(token);
    }

    /**
     * 退出登录
     *
     * @return
     * @throws IOException
     */
    public static Token logout(Token token) throws IOException {
        // 退出Token
        logoutSimple(token);

        // 如果已登录就删除在线用户缓存
        if (ParamUtil.isLogin(token)) {
            // 获取在线用户缓存Key
            String buildOnlineUserCacheKey = buildOnlineUserCacheKey(token);
            // 删除在线用户
            removeUserOnline(buildOnlineUserCacheMapKey(token.getUser().getId()), buildOnlineUserCacheKey);
            // 记录用户退出日志
            recordLogininfor(token.getUser().getUsername(), EStatusLogin.LOGOUT, "退出成功");
        }
        return token;
    }

    /**
     * 退出登录
     *
     * @param userId
     * @throws IOException
     */
    public static List<Token> logout(String userId) throws IOException {
        List<Token> tokens = new ArrayList<>(1);
        // 获取在线用户集合
        Map<String, UserOnline> map = CacheUtil.CACHE_REDIS.getMap(buildOnlineUserCacheMapKey(userId));

        Collection<UserOnline> values = map.values();
        // 遍历并退出登录
        for (UserOnline userOnline : values) {
            // 获取Token
            Token tokenOtherById = getTokenOtherById(userOnline.getTokenId());
            // 退出登录
            tokens.add(logout(tokenOtherById));
        }
        return tokens;
    }

    /**
     * 退出登录
     *
     * @param userIds
     * @throws IOException
     */
    public static List<Token> logout(List<String> userIds) throws IOException {
        List<Token> tokens = new ArrayList<>(1);
        // 遍历并退出登录
        for (String userId : userIds) {
            // 退出登录
            tokens.addAll(logout(userId));
        }
        return tokens;
    }

    /**
     * 记录登录信息
     *
     * @param username 用户名
     * @param status   状态
     * @param message  消息
     * @param args     列表
     * @return 任务task
     */
    public static void recordLogininfor(final String username, final EStatusLogin status,
        final String message, final Object... args) {
        LOG_FACTORY.recordLogininfor(username, status, message, args);
    }

    /**
     * 操作日志记录
     *
     * @param logOper 操作日志信息
     * @return 任务task
     */
    public static void recordOper(final LogOper logOper) {
        LOG_FACTORY.recordOper(logOper);
    }

    /**
     * 获取在线用户
     *
     * @param key
     * @return
     * @throws IOException
     */
    public static UserOnline getUserOnline(String key) throws IOException {
        return CacheUtil.CACHE_REDIS.getByMap(buildOnlineUserCacheMapKey(), key);
    }

    /**
     * 获取在线用户
     *
     * @param key
     * @return
     * @throws IOException
     */
    public static List<UserOnline> getUserOnlines(Token token) throws IOException {
        return CacheUtil.CACHE_REDIS.getsByMap(buildOnlineUserCacheMapKey(token.getUser().getId()), Constant.X);
    }

    /**
     * 设置在线用户
     *
     * @return
     * @throws IOException
     */
    public static void setUserOnline(UserOnline userOnline) throws IOException {
        CacheUtil.CACHE_REDIS.setexByMap(buildOnlineUserCacheMapKey(), userOnline.getChacheKey(), userOnline,
            CacheUtil.getUserTimout());
    }

    /**
     * 删除在线用户
     *
     * @param key
     * @return
     * @throws IOException
     */
    public static void removeUserOnline(String key) throws IOException {
        removeUserOnline(buildOnlineUserCacheMapKey(), key);
    }

    /**
     * 删除在线用户
     *
     * @param key       Token
     * @param mapKey    用户集合
     * @return
     * @throws IOException
     */
    public static void removeUserOnline(String mapKey, String key) throws IOException {
        CacheUtil.CACHE_REDIS.delByMapKey(mapKey, key);
    }

    /**
     * 允许多个帐号同时登录处理
     *
     * @throws IOException
     */
    public static void kickoutAll(Token token) throws IOException {
        String buildCacheKey = buildOnlineUserAllCacheKey(token);

        UserOnline userOnline = new UserOnline();
        userOnline.init(token);
        userOnline.setChacheKey(buildCacheKey);
        // 存储到缓存（这个时间最好和Token的有效期一致或者大于Token的有效期）
        setUserOnline(userOnline);
    }

    /**
     * 不允许多个帐号同时登录处理
     *
     * @throws IOException
     */
    public static void kickoutOne(Token token) throws IOException {
        String buildCacheKey = buildOnlineUserOneCacheKey(token);

        // 从缓存获取用户
        UserOnline userOnline = getUserOnline(buildCacheKey);
        // 如果已有用户登录
        if (StringUtil.isNotBlankObject(userOnline)) {
            String tokenId = token.getId();
            String oldTokenId = userOnline.getTokenId();
            // Token不相同，那么就要处理了
            if (!tokenId.equals(oldTokenId)) {
                /**
                 * 如果用户相同,Token不相同 1.获取到原来的Token,并且标记为踢出 2.将用户TokenID替换为当前TokenID
                 * 3.继续走
                 */
                Token oldToken = getTokenOtherById(oldTokenId);
                if (StringUtil.isNotBlankObject(oldToken)) {
                    // 标记Token已经踢出
                    oldToken.setStatusKickout(Boolean.TRUE);
                    // 更新别人的Token
                    refreshTokenOther(oldToken);
                }

                // 清除旧缓存
                userOnline = null;
            } // Token相同就刷新并继续
        }

        // 如果没有用户登录就保存并继续
        if (StringUtil.isBlankObject(userOnline)) {
            userOnline = new UserOnline();
            userOnline.init(token);
            userOnline.setChacheKey(buildCacheKey);
        }

        // 存储到缓存（这个时间最好和Token的有效期一致或者大于Token的有效期）
        setUserOnline(userOnline);
    }

    /**
     * 用户登录数量限制处理
     *
     * @throws IOException
     */
    public static void kickoutNumber(Token token) throws IOException {
        String tokenId = token.getId();
        String buildCacheKey = buildOnlineUserNumberCacheKey(token);

        // 从缓存获取用户所有的Token
        List<UserOnline> userOnlines = getUserOnlines(token);
        // 当前登录的用户
        UserOnline userOnline = null;
        if (StringUtil.isNotBlankObject(userOnlines) && userOnlines.size() > 0) {// 如果已有用户登录
            // 标记是否包含当前用户
            boolean isContains = false;
            for (UserOnline user : userOnlines) {
                if (tokenId.equals(user.getTokenId())) {
                    isContains = true;
                    userOnline = user;
                    break;
                }
            }
            if (!isContains) {// 如果不包含Token，那么就要处理了
                /**
                * 如果用户编号相同,Token不相同 1.获取到原来的Token，并且标记为踢出 2.将用户TokenId替换为当前TokenId
                * 3.继续走
                */
                // 登录用户数量
                int size = userOnlines.size();
                // 用户允许的最大登录数量
                Integer onlineMaxNumber = token.getUser().getOnlineMaxNumber();
                if (onlineMaxNumber <= size) {// 如果登录的数量达到允许的数量,就踢出最先登录的
                    // 标记要踢多少人
                    int fo = size - onlineMaxNumber + 1;
                    // 根据Token创建时间冒泡排序(从小到大)
                    for (int i = 0; i < size - 1; i++) {
                        for (int j = 0; j < size - i - 1; j++) {
                            UserOnline userOne = userOnlines.get(j);
                            long timeOne = userOne.getLoginTime();
                            UserOnline userTwo = userOnlines.get(j + 1);
                            long timeTwo = userTwo.getLoginTime();
                            if (timeOne > timeTwo) {
                                userOnlines.set(j, userTwo);
                                userOnlines.set(j + 1, userOne);
                            }
                        }
                    }

                    // 踢出最前面的几个
                    for (int i = 0; i < fo; i++) {
                        // 获取旧在线用户
                        UserOnline oldUserOnline = userOnlines.get(i);
                        // 获取旧在线用户的TokenId
                        String oldTokenId = oldUserOnline.getTokenId();
                        // 获取旧在线用户的Token
                        Token oldToken = getTokenOtherById(oldTokenId);
                        if (StringUtil.isNotBlankObject(oldToken)) {
                            // 标记Token已经踢出
                            oldToken.setStatusKickout(Boolean.TRUE);
                            // 更新别人的Token
                            refreshTokenOther(oldToken);
                        }

                        // 删除别人的在线缓存
                        removeUserOnline(buildOnlineUserCacheMapKey(oldUserOnline.getUserId()),
                            oldUserOnline.getChacheKey());
                    }
                } // 如果登录的数量没有达到允许的数量,就保存Token然后继续
            } // 如果已经包含当前Token就刷新Token然后继续
        } // 如果还没有用户登录，就保存Token然后继续

        // 如果没有用户登录就保存并继续
        if (StringUtil.isBlankObject(userOnline)) {
            userOnline = new UserOnline();
            userOnline.init(token);
            userOnline.setChacheKey(buildCacheKey);
        }

        // 存储到缓存（这个时间最好和Token的有效期一致或者大于Token的有效期）
        setUserOnline(userOnline);
    }

    /**
     * 用户登录限制处理
     *
     * @throws IOException
     */
    public static void kickout() throws IOException {
        kickout(getToken());
    }

    /**
     * 用户登录限制处理
     *
     * @param token
     * @throws IOException
     */
    public static void kickout(Token token) throws IOException {
        BaseUser user = token.getUser();
        Integer typeMoreOnline = user.getTypeMoreOnline();

        // 用户不允许帐号同时在线
        if (ELoginMoreOnlineType.ONE.getCode() == typeMoreOnline) {
            kickoutOne(token);
        } else if (ELoginMoreOnlineType.MACHINE_ONE.getCode() == typeMoreOnline) {
            kickoutOne(token);
        } else if (ELoginMoreOnlineType.NUMBER.getCode() == typeMoreOnline) {
            kickoutNumber(token);
        } else if (ELoginMoreOnlineType.MACHINE_NUMBER.getCode() == typeMoreOnline) {
            kickoutNumber(token);
        } else {
            // 用户允许帐号同时在线
            kickoutAll(token);
        }
    }

    /**
     * 构建在线用户缓存key
     *
     * @return
     * @throws IOException
     */
    public static String buildOnlineUserCacheKey() throws IOException {
        // 获取Token
        Token token = getToken();
        // 如果没有登录就返回空
        if (!ParamUtil.isLogin(token)) {
            return null;
        }

        return buildOnlineUserCacheKey(token);
    }

    /**
     * 构建在线用户缓存MapKey
     *
     * @return
     * @throws IOException
     */
    public static String buildOnlineUserCacheMapKey() throws IOException {
        return buildOnlineUserCacheMapKey(getUserId());
    }

    /**
     * 构建在线用户缓存MapKey
     *
     * @param userId
     * @return
     * @throws IOException
     */
    public static String buildOnlineUserCacheMapKey(String userId) throws IOException {
        return ConstantByMap.ONLINE_USER_PREFIX + userId;
    }

    /**
     * 构建在线用户缓存key
     *
     * @return
     * @throws IOException
     */
    public static String buildOnlineUserCacheKey(Token token) throws IOException {
        Integer typeMoreOnline = token.getUser().getTypeMoreOnline();

        // 用户不允许帐号同时在线
        if (ELoginMoreOnlineType.ONE.getCode() == typeMoreOnline) {
            return buildOnlineUserOneCacheKey(token);
        } else if (ELoginMoreOnlineType.MACHINE_ONE.getCode() == typeMoreOnline) {
            return buildOnlineUserOneCacheKey(token);
        } else if (ELoginMoreOnlineType.NUMBER.getCode() == typeMoreOnline) {
            return buildOnlineUserNumberCacheKey(token);
        } else if (ELoginMoreOnlineType.MACHINE_NUMBER.getCode() == typeMoreOnline) {
            return buildOnlineUserNumberCacheKey(token);
        } else {
            // 用户允许帐号同时在线
            return buildOnlineUserAllCacheKey(token);
        }
    }

    /**
     * 构建在线用户缓存key 将用户编号拼接成ONLINE_USER+用户编号+Key
     * (一个用户可能有多个Token,一个在线用户缓存只保存最后一个登录的Token)
     *
     * @return
     * @throws IOException
     */
    public static String buildOnlineUserOneCacheKey() throws IOException {
        Token token = getToken();
        return buildOnlineUserOneCacheKey(token);
    }

    /**
     * 构建在线用户缓存key 将用户编号拼接成ONLINE_USER+用户编号+Key
     * (一个用户可能有多个Token,一个在线用户缓存只保存最后一个登录的Token)
     *
     * @param tokenId
     * @return
     * @throws IOException
     */
    public static String buildOnlineUserOneCacheKey(String tokenId) throws IOException {
        Token token = getTokenById(tokenId);
        return buildOnlineUserOneCacheKey(token);
    }

    /**
     * 构建在线用户缓存key 将用户编号拼接成ONLINE_USER+用户编号+Key
     * (一个用户可能有多个Token,一个在线用户缓存只保存最后一个登录的Token)
     *
     * @param token
     * @return
     * @throws IOException
     */
    public static String buildOnlineUserOneCacheKey(Token token) throws IOException {
        return token.getKeyLoginDevice();
    }

    /**
     * 构建所有在线用户缓存key 将用户编号拼接成ONLINE_USER+用户编号+Key+TokenId
     *
     * @param token
     * @return
     * @throws IOException
     */
    public static String buildOnlineUserAllCacheKey(Token token) throws IOException {
        return buildOnlineUserOneCacheKey(token) + token.getId();
    }

    /**
     * 构建允许一定数量在线用户缓存key 将用户编号拼接成ONLINE_USER+用户编号+Key+TokenId
     *
     * @param token
     * @return
     * @throws IOException
     */
    public static String buildOnlineUserNumberCacheKey(Token token) throws IOException {
        return buildOnlineUserAllCacheKey(token);
    }

    /**
     * 构建允许一定数量在线用户缓存key前缀 将用户编号拼接成ONLINE_USER+用户编号+Key
     *
     * @param token
     * @return
     * @throws IOException
     */
    public static String buildOnlineUserNumberCacheKeyPrefix(Token token) throws IOException {
        return buildOnlineUserOneCacheKey(token);
    }

    /**
     * 获取当前的用户
     *
     * @return
     * @throws IOException
     */
    public static BaseUser getUser() throws IOException {
        return getToken().getUser();
    }

    /**
     * 获取当前的用户(true:不存在就创建,false:存在就获取)
     *
     * @return
     * @throws IOException
     */
    public static String getUserId() throws IOException {
        return getUser().getId();
    }

    /**
     * 获取当前帐号的姓名
     *
     * @return
     * @throws IOException
     */
    public static String getUserName() throws IOException {
        return getUser().getName();
    }

    /**
     * 获取当前帐号最后登录时间
     *
     * @return
     * @throws IOException
     */
    public static Long getLastLoginTime() throws IOException {
        return getToken().getLoginTime();
    }

    /**
     * 根据用户类型获取帐号
     *
     * @return
     * @throws IOException
     */
    public static String getUsernameByType() throws IOException {
        Token token = getToken();
        Set<String> roles = getRoles(token);
        BaseUser user = token.getUser();

        if (roles.contains(ERoleType.ADMIN_SUPER.getKey())) {
            return user.getUsername();
        }
        if (roles.contains(ERoleType.ADMIN.getKey())) {
            return user.getUsername();
        }
        if (roles.contains(ERoleType.LABOR.getKey())) {
            return user.getUsername();
        }
        if (roles.contains(ERoleType.MERCHANT.getKey())) {
            return user.getUsername();
        }
        if (roles.contains(ERoleType.USER.getKey())) {
            return ConstantBean.USER_PAY_SERVICE.getWxOpenid();
        }
        return user.getId() + "";
    }

    // /**
    // * 创建普通用户的时候给用户赋初值
    // * @param userOnlineBo
    // */
    // public static void createUserInitGeneral(UserOnlineBo userOnlineBo, UserOnlineBo user) {
    // user.setId(1L);
    // user.setAccountId("test");
    // user.setUsername("1");
    // try {
    // user.setPassword(EncryptionUtils.encryption("1"));
    // } catch (Exception e) {
    // log.error(e.getMessage(), e);
    // }
    // user.setReuserMe(true);
    // user.setCellphoneComputerMeanwhileOnline(ELoginMoreType.MACHINE_ONE.getCode());
    // user.setOnlineMaxNumber(-1);
    // userOnlineBo.setMoreOnline(ELoginMoreType.MACHINE_ONE.getCode());
    // user.setCellphoneOrComputer(0);
    // user.setLastLoginTime(new Date());
    // userOnlineBo.setStatus(ELoginBannedType.NORMAL.getCode());
    // }
    //
    // /**
    // * 创建第三方用户的时候给用户赋初值
    // * @param userOnlineBo 要初始化的用户
    // * @param otherLogin 页面传过来的参数
    // * @param otherUser 获取到的第三方用户
    // */
    // public static void createUserInitOther(UserOnlineBo userOnlineBo, OtherUser otherUser) {
    // userOnlineBo.setAccountId(otherUser.getAppId());
    // userOnlineBo.setStatus(ELoginBannedType.NORMAL.getCode());
    // userOnlineBo.setMoreOnline(ELoginMoreType.MACHINE_ONE.getCode());
    // userOnlineBo.setCellphoneComputerMeanwhileOnline(ELoginMoreType.MACHINE_ONE.getCode());
    // userOnlineBo.setUsername(GUIDUtils.generateGuid());
    // userOnlineBo.setPassword(Constant.USER_PASSWORD_DEFAULT);
    // userOnlineBo.setReuserMe(false);
    // }
    //
    // /**
    // * 创建手机或邮箱用户的时候给用户赋初值
    // * @param userOnlineBo
    // */
    // public static void createUserInitMobile(UserOnlineBo userOnlineBo) {
    //
    // }

    // /**
    // * 用户登录后给用户赋初值(第三方登录方式)
    // *
    // * @param user 要赋值的对象
    // * @param loginParam 客户端传过来的参数
    // */
    // public static void userLoginInitOther(BaseUser user, LoginParam loginParam) {
    // UserOnlineBo userOnlineBo = (UserOnlineBo)user.getAccount();
    // // 设置登录方式
    // userOnlineBo.setCellphoneOrComputer(loginParam.getCellphoneOrComputer());
    //
    // userLoginInit(userOnlineBo);
    // }

    // /**
    // * 第三方登录
    // *
    // * @param otherLogin
    // * @return
    // * @throws Exception
    // */
    // public static void submitLoginOther(LoginParam loginParam) throws Exception {
    // CustomToken customToken =
    // new CustomToken(loginParam.getUsername(), loginParam.getPassword(), loginParam.getReuserMe());
    // customToken.setLoginType(CustomToken.OTHER_LOGIN);
    // customToken.setLoginParam(loginParam);
    // // 执行ShiroRealm
    // SecurityUtils.getSubject().login(customToken);
    //
    // submitLogin();
    // }

    /**
     * 判断登录的用户是否被禁用
     *
     * @param user
     * @return
     */
    public static boolean submitLoginIsDisable(BaseUser user) {
        if (StringUtil.isNotBlankObject(user)) {
            if (user.getStatus() == EGeneralStatus.DISABLE.getCode()) {
                return true;
            }
            if (user.getDisableAtType() == EDisableAtType.TIME_LIMIT.getCode()
                && user.getDisableAt().getTime() < DateUtil.nowLong()) {
                return true;
            }
        }
        return false;
    }

    /**
     * 判断登录的用户是否启用
     *
     * @param user
     */
    public static boolean submitLoginIsNormal(BaseUser user) {
        return !submitLoginIsDisable(user);
    }

    /**
     * 获取用户的角色
     *
     * @return
     * @throws IOException
     */
    public static Set<String> getRoles() throws IOException {
        return getRoles(getToken());
    }

    /**
     * 获取用户的角色
     *
     * @param token
     * @return
     * @throws IOException
     */
    public static Set<String> getRoles(Token token) throws IOException {
        return token.getRoles();
    }

    /**
     * 获取用户的角色
     *
     * @param roleIds   角色ID
     * @return
     * @throws IOException
     */
    public static Set<String> getRoles(Set<String> roleIds) throws IOException {
        Set<String> keys = new HashSet<>();
        for (String roleId : roleIds) {
            String key = ROLE_KEYS.get(roleId);
            if (key != null) {
                keys.add(key);
            }
        }
        return keys;
    }

    /**
     * 获取用户的角色ID
     *
     * @return
     * @throws IOException
     */
    public static Set<String> getRoleIds() throws IOException {
        return getToken().getRoleIdsNew();
    }

    /**
     * 获取用户的角色ID
     *
     * @return
     * @throws IOException
     */
    public static Set<String> getRoleIdsExist() throws IOException {
        return getToken().getRoleIdsExist();
    }

    /**
     * 获取用户的角色名称
     *
     * @return
     * @throws IOException
     */
    public static Set<String> getRoleNames() throws IOException {
        return getRoleNames(getToken());
    }

    /**
     * 获取用户的角色名称
     *
     * @param token
     * @return
     * @throws IOException
     */
    public static Set<String> getRoleNames(Token token) throws IOException {
        Set<String> roleIds = token.getRoleIdsNew();

        Set<String> roleNames = new HashSet<>();
        for (String roleId : roleIds) {
            String roleName = ROLE_NAMES.get(roleId);
            if (roleName != null) {
                roleNames.add(roleName);
            }
        }
        return roleNames;
    }

    /**
     * 获取用户的权限
     *
     * @return
     * @throws IOException
     */
    public static Set<String> getPerms() throws IOException {
        return getToken().getPermissions();
    }

    /**
     * 获取用户的权限ID
     *
     * @return
     * @throws IOException
     */
    public static Set<String> getPermIds() throws IOException {
        // 超级管理员返回特殊权限
        Set<String> roleIds = getToken().getRoleIdsNew();
        Set<String> permIds = new HashSet<>();
        for (String roleId : roleIds) {
            Set<String> set = PERM_IDS.get(roleId);
            if (set != null) {
                permIds.addAll(set);
            }
        }
        return permIds;
    }

    /**
     * 获取用户的权限ID
     *
     * @return
     * @throws IOException
     */
    public static Set<String> getPermIdsExist() throws IOException {
        // 超级管理员返回特殊权限
        Set<String> roleIds = getToken().getRoleIdsExist();
        Set<String> permIds = new HashSet<>();
        for (String roleId : roleIds) {
            Set<String> set = PERM_IDS_EXIST.get(roleId);
            if (set != null) {
                permIds.addAll(set);
            }
        }
        return permIds;
    }

    /**
     * 获取用户的权限
     *
     * @param roleIds   角色ID
     * @return
     * @throws IOException
     */
    public static Set<String> getPerms(Set<String> roleIds) throws IOException {
        Set<String> perms = new HashSet<>();
        for (String roleId : roleIds) {
            Set<String> set = PERMS.get(roleId);
            if (set != null) {
                perms.addAll(set);
            }
        }
        return perms;
    }

    /**
     * 获取用户的部门
     *
     * @return
     * @throws IOException
     */
    public static Set<String> getDepts() throws IOException {
        return getToken().getDepts();
    }

    /**
     * 清空当前用户权限信息。 目的:为了在判断权限的时候，再次会再次 <code>doGetAuthorizationInfo(...)  </code>方法。 ps: 当然你可以手动调用
     * <code> doGetAuthorizationInfo(...)  </code>方法。
     * 这里只是说明下这个逻辑，当你清空了权限，<code> doGetAuthorizationInfo(...)  </code>就会被再次调用。
     */
    public static void clearNowUserAuth() {
        /**
         * 这里需要获取到shrio.xml 配置文件中，对Realm的实例化对象。才能调用到 Realm 父类的方法。
         */
        /**
         * 获取当前系统的Realm的实例化对象，方法一（通过 @link org.apache.shiro.web.mgt.DefaultWebSecurityManager
         * 或者它的实现子类的{Collection<Realm> getRealms()}方法获取）。 获取到的时候是一个集合。Collection<Realm> RealmSecurityManager
         * securityManager = (RealmSecurityManager) SecurityUtils.getSecurityManager(); SampleRealm realm =
         * (SampleRealm)securityManager.getRealms().iterator().next();
         */
        /**
         * 方法二、通过ApplicationContext 从Spring容器里获取实列化对象。
         */
        // ConstantBean.SHIRO_REALM.clearCachedAuthorizationInfo();
        /**
         * 当然还有很多直接或者间接的方法，此处不纠结。
         */
    }

    /**
     * 根据UserIds 清空权限信息。
     *
     * @param id
     *            用户ID
     */
    public static void clearUserAuthByUserId(Long... userIds) {

        // if (isBlankObject(userIds) || userIds.length == 0) {
        // return;
        // }
        // List<SimplePrincipalCollection> result = SessionUtils.getSimplePrincipalCollectionByUserId(userIds);
        //
        // for (SimplePrincipalCollection simplePrincipalCollection : result) {
        // ConstantBean.SHIRO_REALM.clearCachedAuthorizationInfo(simplePrincipalCollection);
        // }
    }

    /**
     * 方法重载
     *
     * @param userIds
     */
    public static void clearUserAuthByUserId(List<Long> userIds) {
        if (isBlankObject(userIds) || userIds.size() == 0) {
            return;
        }
        clearUserAuthByUserId(userIds.toArray(new Long[0]));
    }

    /**
     * 把值放入到当前的Request里
     *
     * @param key
     * @param value
     */
    private static void setValRequest(String key, Object value) {
        ServletUtil.setValRequest(key, value);
    }

    /**
     * 从当前的Request里取值
     *
     * @param key
     * @return
     */
    private static Object getValRequest(String key) {
        return ServletUtil.getValRequest(key);
    }

    /**
     * 删除Request里的值
     *
     * @param key
     * @return
     */
    private static void removeValRequest(String key) {
        ServletUtil.removeValRequest(key);
    }

    /**
     * 从当前的请求头里取值
     *
     * @param key
     * @return
     */
    private static String getReqHeader(String key) {
        return ServletUtil.getReqHeader(key);
    }

    /**
     * 把值放入到响应头中
     *
     * @param value
     */
    private static void setClaimsStrToResHeader(String value) {
        HttpServletResponse response = ServletUtil.getHttpServletResponse();
        // 存到响应头中
        response.setHeader(ConstantByMap.TOKEN_NAME, value);
    }

    /**
     * 从当前的响应头里取值
     *
     * @param key
     * @return
     */
    private static String getResHeader(String key) {
        return ServletUtil.getResHeader(key);
    }

    /**
     * 判断一个字符串是否是空(空对象和空字符串和值为"null")
     *
     * @param obj
     * @return
     */
    private static boolean isBlankString(String str) {
        return StringUtil.isBlankString(str);
    }

    /**
     * 判断一个对象是否是空(空对象)
     *
     * @param obj
     * @return
     */
    private static boolean isBlankObject(Object obj) {
        return StringUtil.isBlankObject(obj);
    }

    /**
     * 判断一个对象是否不是空(空对象)
     *
     * @param obj
     * @return
     */
    private static boolean isNotBlankObject(Object obj) {
        return StringUtil.isNotBlankObject(obj);
    }

    /**
     * 从缓存中获取值
     *
     * @param <T>
     * @param key
     * @return
     */
    private static <T> T get(String key) {
        try {
            return CacheUtil.get(key);
        } catch (IOException e) {
            ExpUtil.throwEx(e);
            return null;
        }
    }

    /**
     * 往缓存中存储值,带过期时间(不设置为永久存储,此方法传负数会报错,为了提升性能去掉了if判断,确定不会有负数就调这个方法)
     *
     * @param key
     * @param value
     * @param timer 过期时间(以秒为单位)
     */
    private static void setex(String key, Object value, int timer) {
        try {
            CacheUtil.setex(key, value, timer);
        } catch (IOException e) {
            ExpUtil.throwEx(e);
        }
    }

    /**
     * 删除某个数据
     *
     * @param dkey
     * @return
     * @throws IOException
     */
    private static void delByKey(String key) throws IOException {
        CacheUtil.delByKey(key);
    }

}